/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia;

import org.sourceprojects.lycia.exceptions.LyciaInitializationException;

/**
 * Defines a factory API that enables applications to obtain instances of the
 * {@link org.sourceprojects.lycia.LyciaParser} either by given standard
 * implementations or by configuring own implementations.
 * 
 * @author noctarius
 * @since 0.0.1
 */
public final class LyciaParserFactory {
	/**
	 * Classname for the standard {@link org.sourceprojects.lycia.LyciaParser}
	 * class to be created if no special implementation is given
	 */
	public static final String STANDARD_LYCIA_PARSER_CLASS = "org.sourceprojects.lycia.internal.LyciaParserImpl";

	/**
	 * Classname for the Android compatible
	 * {@link org.sourceprojects.lycia.LyciaParser} class to be created
	 */
	public static final String ANDROID_COMPATIBLE_LYCIA_PARSER_CLASS = "org.sourceprojects.lycia.internal.AndroidCompatibleLyciaParserImpl";

	/**
	 * /** Classname for the standard, threadsafe XmlParser
	 * {@link org.sourceprojects.lycia.ParserContext} class to be created if no
	 * special implementation is given
	 * 
	 * @since 1.9.5
	 */
	public static final String STANDARD_PARSERCONTEXT_THREADSAFE_CLASS = "org.sourceprojects.lycia.internal.ThreadsafeParserContextImpl";

	/**
	 * Classname for the standard XmlParser
	 * {@link org.sourceprojects.lycia.ParserContext} class to be created if no
	 * special implementation is given
	 */
	public static final String STANDARD_PARSERCONTEXT_CLASS = "org.sourceprojects.lycia.internal.ParserContextImpl";

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} are the standard internal
	 * ones. The classloader to load up those implementations is the actual
	 * ContextClassLoader of the actual thread.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @return New instance of standard XmlParser implementation
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 */
	public static final <B> LyciaParser<B> newInstance() {
		return newInstance(false);
	}

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} are the standard internal
	 * ones. The classloader to load up those implementations is the actual
	 * ContextClassLoader of the actual thread.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @param threadsafe
	 *            Define if the internal used implementation should be fully
	 *            threadsafe or not
	 * @return New instance of standard XmlParser implementation
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 * @since 1.9.5
	 */
	public static final <B> LyciaParser<B> newInstance(final boolean threadsafe) {

		final ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();

		if (threadsafe) {
			return newInstance(STANDARD_LYCIA_PARSER_CLASS,
					STANDARD_PARSERCONTEXT_THREADSAFE_CLASS, classLoader);
		} else {
			return newInstance(STANDARD_LYCIA_PARSER_CLASS,
					STANDARD_PARSERCONTEXT_CLASS, classLoader);
		}
	}

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} are the standard internal
	 * ones. The classloader to load up those implementations is the given
	 * classloader.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @param classLoader
	 *            The classloader to load classes
	 * @return New instance of standard XmlParser implementation
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 */
	public static final <B> LyciaParser<B> newInstance(
			final ClassLoader classLoader) {
		return newInstance(classLoader, false);
	}

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} are the standard internal
	 * ones. The classloader to load up those implementations is the given
	 * classloader.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @param classLoader
	 *            The classloader to load classes
	 * @param threadsafe
	 *            Define if the internal used implementation should be fully
	 *            threadsafe or not
	 * @return New instance of standard XmlParser implementation
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 * @since 1.9.5
	 */
	public static final <B> LyciaParser<B> newInstance(
			final ClassLoader classLoader, final boolean threadsafe) {

		if (threadsafe) {
			return newInstance(STANDARD_LYCIA_PARSER_CLASS,
					STANDARD_PARSERCONTEXT_THREADSAFE_CLASS, classLoader);
		} else {
			return newInstance(STANDARD_LYCIA_PARSER_CLASS,
					STANDARD_PARSERCONTEXT_CLASS, classLoader);
		}
	}

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} must be given as
	 * parameters to the method. The classloader to load up those
	 * implementations is the given classloader.<br>
	 * {@link LyciaInitializationException} will be thrown if one of both
	 * implementations is null or not found.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @param parserImplementationClassname
	 *            Classname of the implementation used for XmlParser
	 * @param contextImplementationClassname
	 *            Classname of the implementation used for ParserContext
	 * @param classLoader
	 *            The classloader to load classes
	 * @return New instance of standard XmlParser implementation
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 */
	public static final <B> LyciaParser<B> newInstance(
			final String parserImplementationClassname,
			final String contextImplementationClassname,
			final ClassLoader classLoader) {

		return newInstance(parserImplementationClassname,
				contextImplementationClassname, classLoader, true);
	}

	/**
	 * Obtain a new instance of a {@link org.sourceprojects.lycia.LyciaParser} .<br>
	 * Implementations that used for
	 * {@link org.sourceprojects.lycia.LyciaParser} and
	 * {@link org.sourceprojects.lycia.ParserContext} must be given as
	 * parameters to the method. The classloader to load up those
	 * implementations is the given classloader.<br>
	 * {@link LyciaInitializationException} will be thrown if one of both
	 * implementations is null or not found.
	 * 
	 * @param <B>
	 *            Type of output class to use in parsing and transforming
	 * @param parserImplementationClassname
	 *            Classname of the implementation used for XmlParser
	 * @param contextImplementationClassname
	 *            Classname of the implementation used for ParserContext
	 * @param classLoader
	 *            The classloader to load classes
	 * @param createResourceResolver
	 *            Automatically create a basic ResourceResolver for the
	 * @return New instance of standard XmlParser implementation XmlParser
	 *         instance
	 * @throws LyciaInitializationException
	 *             If some exception occurs on initialization of the new
	 *             instance the exception is wrapped into a
	 *             {@link LyciaInitializationException}
	 * @since 0.0.6
	 */
	@SuppressWarnings("unchecked")
	public static final <B> LyciaParser<B> newInstance(
			final String parserImplementationClassname,
			final String contextImplementationClassname,
			final ClassLoader classLoader, final boolean createResourceResolver) {

		try {
			Class<? extends LyciaParser<B>> parserClass;
			Class<? extends ParserContext<B>> contextClass;
			parserClass = (Class<? extends LyciaParser<B>>) classLoader
					.loadClass(parserImplementationClassname);

			contextClass = (Class<? extends ParserContext<B>>) classLoader
					.loadClass(contextImplementationClassname);

			final LyciaParser<B> parser = parserClass.newInstance();
			final ParserContext<B> context = contextClass.newInstance();
			parser.setParserContext(context);

			if (createResourceResolver) {
				parser.setResourceResolver(ResourceResolverFactory
						.newInstance(classLoader));
			}

			return parser;
		} catch (final IllegalAccessException e) {
			e.printStackTrace();
			throw new LyciaInitializationException(
					"Parser initialization failed", e);
		} catch (final InstantiationException e) {
			e.printStackTrace();
			throw new LyciaInitializationException(
					"Parser initialization failed", e);
		} catch (final ClassNotFoundException e) {
			e.printStackTrace();
			throw new LyciaInitializationException(
					"Parser initialization failed", e);
		}
	}

	private LyciaParserFactory() {
	}
}
